package ar.edu.itba.pod.legajo45129;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.PropertyConfigurator;

import ar.edu.itba.pod.legajo45129.simul.communication.ClusterAdministrationImpl;
import ar.edu.itba.pod.legajo45129.simul.communication.SimulationCommunicationImpl;
import ar.edu.itba.pod.legajo45129.simul.communication.factory.MessageFactory;
import ar.edu.itba.pod.simul.ObjectFactoryAlternative;
import ar.edu.itba.pod.simul.communication.Message;
import ar.edu.itba.pod.simul.market.Market;
import ar.edu.itba.pod.simul.market.MarketManager;
import ar.edu.itba.pod.simul.market.Resource;
import ar.edu.itba.pod.simul.simulation.Agent;
import ar.edu.itba.pod.simul.simulation.SimulationManager;
import ar.edu.itba.pod.simul.time.TimeMapper;
import ar.edu.itba.pod.simul.time.TimeMappers;
import ar.edu.itba.pod.simul.ui.ConsoleFeedbackCallback;
import ar.edu.itba.pod.simul.ui.FeedbackCallback;
import ar.edu.itba.pod.simul.ui.FeedbackMarketManager;
import ar.edu.itba.pod.simul.ui.FeedbackSimulationManager;
import ar.edu.itba.pod.simul.units.Factory;
import ar.edu.itba.pod.simul.units.SimpleConsumer;
import ar.edu.itba.pod.simul.units.SimpleProducer;

/**
 * Simulation main program
 */
public class SingleNodeSimulationApp implements Runnable {	
	private static Option host;
	private static Options options = new Options();
	private static Option connectedNodes;
	private static Option agents;
	private static Option connect;
	private static Option createGroup;
	private static Option disconnect;
	private static Option help;
	private static CommandLineParser parser = new GnuParser();
	private static Option startSimul;
	private static Option addAgents;
	private static HelpFormatter formatter = new HelpFormatter();
	private NodeId node;
	private static Option addConsumer;
	private static Option addProducer;
	private static Option addAgents2;
	private static Option stat;
	
	public SingleNodeSimulationApp() {
	}

	public static void main(String[] args) {
		PropertyConfigurator.configure("config/log4j.properties");
		 
//		try {
//			if (System.getSecurityManager() == null) {
//				System.setSecurityManager(new SecurityManager());
//			}
//		} catch (SecurityException e) {
//			System.err.println("Error: could not set security manager: " + e);
//		}

		initOptions();
		
		SingleNodeSimulationApp simul = new SingleNodeSimulationApp();
		try {
			CommandLine cmd = parser.parse(options, args);
			if (cmd.hasOption(host.getOpt())) {
				simul.setNode(new NodeId(cmd.getOptionValue(host.getOpt())));
			} else {
				throw new ParseException("-host argument missing");
			}
		} catch (ParseException e) {
			formatter.printHelp("java -jar simul.jar <-host> ip [OPTIONS]", options);
			System.exit(1);
		}
		
		simul.run();
	}

	private void setNode(NodeId nodeId) {
		node = nodeId;
	}

	@Override
	public void run() {
		FeedbackCallback callback = new ConsoleFeedbackCallback();
		TimeMapper timeMapper = TimeMappers.oneSecondEach(6, TimeUnit.HOURS);
		
		ObjectFactoryAlternative objectFactory = new MyObjectFactoryAlternative(node.toString(), timeMapper);
		
		MarketManager marketManager = objectFactory.getMarketManager();
		marketManager = new FeedbackMarketManager(callback, marketManager);

		marketManager.start();
		Market market = marketManager.market();
		
		SimulationManager sim = objectFactory.getSimulationManager();
		sim = new FeedbackSimulationManager(callback, sim);
		sim.register(Market.class, market);
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		CommandLine cmd;
		
		while (true) {
			String line;
			try {
				line = br.readLine();
				String[] args = line.split(" ");
				
				cmd = parser.parse(options, args);
				
				if (cmd.hasOption(connect.getOpt())) {
					objectFactory.connectToGroup(cmd.getOptionValue(connect.getOpt()));
				}
				
				if (cmd.hasOption(createGroup.getOpt())) {
					objectFactory.createGroup();
				}
				
				if (cmd.hasOption(startSimul.getOpt())) {
					sim.start();
					try {
						Thread.sleep(1000 * 20);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					sim.shutdown();
					
					marketManager.shutdown();
				}
				
				if (cmd.hasOption(addAgents.getOpt())) {
					// Define simulation agents
					Resource pigIron = new Resource("Mineral", "Pig Iron");
					Resource copper = new Resource("Mineral", "Copper");
					Resource steel = new Resource("Alloy", "Steel");
					
					Agent mine1 = SimpleProducer.named("pig iron mine")
						.producing(2).of(pigIron)
						.every(12, TimeUnit.HOURS)
						.build();
					Agent mine2 = SimpleProducer.named("copper mine")
						.producing(4).of(copper)
						.every(1, TimeUnit.DAYS)
						.build();
					Agent refinery = Factory.named("steel refinery")
						.using(5, pigIron).and(2, copper)
						.producing(6, steel)
						.every(1, TimeUnit.DAYS)
						.build();
					Agent factory = SimpleConsumer.named("factory")
						.consuming(10).of(steel)
						.every(2, TimeUnit.DAYS)
						.build();

					sim.addAgent(mine1);
					sim.addAgent(mine2);
					sim.addAgent(refinery);
					sim.addAgent(factory);
//					try {
//						Thread.sleep(1000 * 25);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					sim.shutdown();
					
				}
				
				if (cmd.hasOption(addAgents2.getOpt())) {
					// Define simulation agents
					Resource pigIron = new Resource("Mineral", "Pig Iron");
					
					Agent mine1 = SimpleProducer.named("pig iron mine")
						.producing(2).of(pigIron)
						.every(12, TimeUnit.HOURS)
						.build();
					Agent factory = SimpleConsumer.named("factory")
						.consuming(10).of(pigIron)
						.every(2, TimeUnit.DAYS)
						.build();
					
					sim.addAgent(mine1);
					sim.addAgent(factory);
					
				}
				
				if (cmd.hasOption(addConsumer.getOpt())) {
					// Define simulation agents
					Resource pigIron = new Resource("Mineral", "Pig Iron");
					
					Agent consumer = SimpleConsumer.named("consumer")
						.consuming(10).of(pigIron)
						.every(2, TimeUnit.DAYS)
						.build();
					
					sim.addAgent(consumer);
					
				}
				
				if (cmd.hasOption(addProducer.getOpt())) {
					// Define simulation agents
					Resource copper = new Resource("Mineral", "Copper");
					Agent producer = SimpleProducer.named("copper mine")
					.producing(4).of(copper)
					.every(1, TimeUnit.DAYS)
					.build();
					
					sim.addAgent(producer);
					
				}
				
				if (cmd.hasOption(connectedNodes.getOpt())) {
					// Define simulation agents
					System.out.println("known nodes: ");
					ClusterAdministrationImpl ca = (ClusterAdministrationImpl) objectFactory.getConnectionManager().getClusterAdmimnistration();
					Iterator<String> connectedNodes = ca.getConnectedNodes();
					while (connectedNodes.hasNext()) {
						System.out.println(connectedNodes.next());
					}
					
				}
				
				
				
				if (cmd.hasOption(agents.getOpt())) {
					SimulationCommunicationImpl sci = (SimulationCommunicationImpl) objectFactory.getConnectionManager().getSimulationCommunication();
					System.out.println("agents in this node: " + sci.getAgentsCount());
				}
				
				if (cmd.hasOption(disconnect.getOpt())) {
					objectFactory.disconnect();
					System.out.println("node disconnected");
				}
				
				if (cmd.hasOption(help.getOpt())) {
					formatter.printHelp("java -jar simul.jar <-host> ip [OPTIONS]", options);
				}
				
				if (cmd.hasOption(stat.getOpt())) {
					Message message = MessageFactory.NodeMarketDataRequest(node.toString());
					objectFactory.getConnectionManager().getGroupCommunication().broadcast(message);
				}
			} catch (IOException e) {
				throw new RuntimeException("IO error: " + e.getMessage());
			} catch (ParseException e) {
				System.out.println("wrong command");
			}	
		}		
	}
	
	private static void initOptions() {
		host = new Option("host", "node ip");
		host.setArgs(1);
		options.addOption(host);
		
		connectedNodes = new Option("connectedNodes", "show my connected nodes");
		options.addOption(connectedNodes);
		
		agents = new Option("agents", "get agent count");
		options.addOption(agents);
		
		// option group for connect and create cuz they r mutually exclusive options.
		connect = new Option("connect", "connect to group with given node id (URI) as entry point");
		connect.setArgs(1);
		options.addOption(connect);
		
		createGroup = new Option("createGroup", "create a group for this node");
		options.addOption(createGroup);
		
		disconnect = new Option("disconnect", "disconnect from the group");
		options.addOption(disconnect);
		
		help = new Option("help", "print options");
		options.addOption(help);
		
		startSimul = new Option("startSimul", "start the simulation");
		options.addOption(startSimul);
		
		addAgents = new Option("addAgents", "add predefined agents");
		options.addOption(addAgents);
		
		addAgents2 = new Option("addAgents2", "add predefined agents");
		options.addOption(addAgents2);
		
		stat = new Option("stat", "ask for statistics");
		options.addOption(stat);
		
		addConsumer = new Option("ac", "add consumer");
		options.addOption(addConsumer);
		
		addProducer = new Option("ap", "add producer");
		options.addOption(addProducer);		
	}
}
